package org.hawk.gwt.ppc.introspection.impl;

import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import org.hawk.gwt.ppc.introspection.IntrospectionException;
import org.hawk.gwt.ppc.introspection.IntrospectionUtils;
import org.hawk.gwt.ppc.introspection.JavaTypeScope;
import org.hawk.gwt.ppc.introspection.JavaTypeVariable;
import org.hawk.gwt.ppc.parser.JavaKeywords;
import org.hawk.gwt.ppc.utils.Name;

/**
 * Utility class for generic types.
 * Provides routines to resolve and build generic types.
 * 
 * @author alex.bereznevatiy@gmail.com
 *
 */
final class GenericHelper {
	
	private GenericHelper(){}
	
	/**
	 * @param type
	 * @param scope
	 * @return generic type scope for reflection type in context of scope passed.
	 */
	static JavaTypeScope buildGenericType(Type type, JavaScopeImpl scope) {
		if (type instanceof Class) {
			return IntrospectionUtils.getType((Class<?>)type);
		}
		
		return buildGenericType(type.toString(), scope);
	}

	/**
	 * @param type
	 * @param scope
	 * @return generic type scope for type name in context of scope passed.
	 */
	static JavaTypeScope buildGenericType(String type, JavaScopeImpl scope) {
		String name = type;
		String rawName = Name.getRawName(name);
		String parameters = Name.getTypeParameters(name);

		JavaTypeScope raw = scope.resolveType(rawName, scope.getCompilationUnit());
		
		if (parameters.length() == 0) {
			return raw;
		}
		
		return new JavaParameterizedGenericTypeScopeImpl(raw, scope, parameters);
	}
	
	/**
	 * @param vars
	 * @return type variables for reflection TypeVariable's array.
	 */
	static List<JavaTypeVariable> buildTypeVariables(TypeVariable<?>[] vars, JavaScopeImpl owner) {
		if (vars == null || vars.length == 0) {
			return Collections.emptyList();
		}
		List<JavaTypeVariable> result = new ArrayList<JavaTypeVariable>(vars.length);
		for (TypeVariable<?> var : vars) {
			List<String> bounds = new LinkedList<String>();
			for (Type bound : var.getBounds()) {
				bounds.add(getTypeName(bound));
			}
			result.add(new JavaTypeVariableImpl(var.getName(), bounds, 
					IntrospectionUtils.getType((Class<?>)var.getGenericDeclaration()), owner));
		}
		return Collections.unmodifiableList(result);
	}

	private static String getTypeName(Type type) {
		if (type instanceof Class) {
			return ((Class<?>)type).getName();
		}
		return type.toString();
	}
	
	/**
	 * @param params
	 * @return type variables for parameters
	 */
	static List<JavaTypeVariable> buildTypeVariables(String params, JavaScopeImpl owner) {
		if (params.length() == 0) {
			return Collections.emptyList();
		}
		ArrayList<JavaTypeVariable> result = new ArrayList<JavaTypeVariable>(2);
		JavaTokenizer tokenizer = new JavaTokenizer(params);
		readTypeParameters(tokenizer, result, owner);
		result.trimToSize();
		return Collections.unmodifiableList(result);
	}
	
	private static void readTypeParameters(JavaTokenizer tokenizer, 
			List<JavaTypeVariable> result, JavaScopeImpl owner) {
		if (!tokenizer.hasNext()) {
			return;
		}
		if (!tokenizer.next().equals("<")) {
			throw new IntrospectionException("Not a type parameters");
		}
		readSingleTypeParameter(tokenizer, result, owner);
		
		int deep = 1;
		while (deep > 0) {
			String token = tokenizer.current();
			char tok = token.charAt(0);
			if (tok == '<') {
				deep++;
				tokenizer.next();
			} else if (tok == '>') {
				deep--;
				if (!tokenizer.hasNext() && deep == 0) {
					return;
				}
				tokenizer.next();
			} else if (deep == 1 && tok == ',') {
				readSingleTypeParameter(tokenizer, result, owner);
			} else {
				throw new IntrospectionException("Parameter is not fully parsed");
			}
		}
	}
	
	private static void readSingleTypeParameter(JavaTokenizer tokenizer, 
			List<JavaTypeVariable> result, JavaScopeImpl owner) {
		String name = tokenizer.next();
		List<String> bounds = new ArrayList<String>(1);
		if (!tokenizer.next().equals(JavaKeywords.EXTENDS)) {
			result.add(new JavaTypeVariableImpl(name, bounds, IntrospectionUtils.getType(Object.class), owner));
			return;
		}
		do {
			int deep = 0;
			String tok = tokenizer.next();
			StringBuilder typeName = new StringBuilder();
			while (deep > 0 || (!tok.equals("&") && !tok.equals(",") && !tok.equals(">"))) {
				if (tok.equals(">")) {
					deep--;
				} else if (tok.equals("<")) {
					deep++;
				}
				if (tok.equals(JavaKeywords.EXTENDS) || tok.equals(JavaKeywords.SUPER) || tok.equals("&")) {
					typeName.append(' ').append(tok).append(' ');
				} else {
					typeName.append(tok);
				}
				tok = tokenizer.next();
			}
			bounds.add(typeName.toString());
		} while (tokenizer.current().equals("&"));
		
		result.add(new JavaTypeVariableImpl(name, bounds, null, owner));
	}

	/**
	 * Parses type parameters into introspection types.
	 * @param params
	 * @return list of parameter types for passed type parameters 
	 */
	static List<JavaTypeScope> buildTypeParams(String params, JavaScopeImpl owner) {
		if (params.length() < 3) {
			throw new IntrospectionException("Bad type parameters: " + params);
		}
		List<JavaTypeScope> paramTypes = new ArrayList<JavaTypeScope>(1);
		JavaTokenizer tokenizer = new JavaTokenizer(params.substring(1, params.length() - 1));
		JavaTypeScope param;
		while ((param = parseParam(tokenizer, owner)) != null) {
			paramTypes.add(param);
		}
		return Collections.unmodifiableList(paramTypes);
	}

	private static JavaTypeScope parseParam(JavaTokenizer tokenizer, JavaScopeImpl owner) {
		if (!tokenizer.hasNext()) {
			return null;
		}
		String token = tokenizer.next();
		// on this stage we can filter out all the wildcard params:
		if (token.equals("?")) {
			return parseWildcardParam(tokenizer, owner);
		}
		return parseUnknownType(tokenizer, owner);
	}

	private static JavaTypeScope parseUnknownType(JavaTokenizer tokenizer, JavaScopeImpl owner) {
		String token = tokenizer.current();
		/*
		 * Get type name:
		 */
		StringBuilder typeName = new StringBuilder();
		while (!token.equals(",") && 
				!token.equals("<") && 
				!token.equals(">")) {
			typeName.append(token);
			if (!tokenizer.hasNext()) {
				token = "";
				break;
			}
			token = tokenizer.next();
		}
		
		String rawType = typeName.toString();
		/*
		 * In case parameter is raw type or array of raw types, so we are done:
		 */
		if (token.isEmpty() || token.equals(",") || token.equals(">")) {
			return parseTypeOrTypeVariable(rawType, owner);
		} else if (!token.equals("<")) {
			throw new IntrospectionException("Bad parameters");
		}
		JavaTypeScope type = owner.resolveType(rawType, owner.getCompilationUnit());
		/*
		 * In case parameter is parameterized type - we need to get type parameters
		 */
		type = new JavaParameterizedGenericTypeScopeImpl(type, owner, getNestedParameters(tokenizer, token, owner));
		if (!tokenizer.hasNext()) {
			return type;
		}
		token = tokenizer.next();
		if (!token.equals("[")) {
			return type;
		}
		return parseGenericArrayParam(tokenizer, type, owner);
	}

	private static JavaTypeScope parseTypeOrTypeVariable(String rawType, JavaScopeImpl owner) {
		String pureType = Name.getPureClassName(rawType);
		JavaTypeVariable var = owner.resolveTypeParameter(pureType);
		if (var == null) {
			return owner.resolveType(rawType, owner.getCompilationUnit());
		}
		int deep = (rawType.length() - pureType.length()) / 2;
		JavaTypeScope result = var;
		while (deep --> 0) {
			result = new JavaArrayGenericTypeScopeImpl(result, owner);
		}
		return result;
	}

	private static JavaTypeScope parseGenericArrayParam(JavaTokenizer tokenizer, JavaTypeScope type, JavaScopeImpl owner) {
		if (!tokenizer.next().equals("]")) {
			throw new IntrospectionException("Bad parameters"); 
		}
		JavaTypeScope result = new JavaArrayGenericTypeScopeImpl(type, owner);
		if (tokenizer.hasNext() && tokenizer.next().equals("[")) {
			return parseGenericArrayParam(tokenizer, result, owner);
		}
		return result;
	}

	private static String getNestedParameters(JavaTokenizer tokenizer, String token, JavaScopeImpl owner) {
		StringBuilder params = new StringBuilder();
		params.append(token);
		int deep = 1;
		while (deep > 0) {
			if (!tokenizer.hasNext()) {
				throw new IntrospectionException("Bad parameters");
			}
			token = tokenizer.next();
			if (token.equals("<")) {
				deep ++;
			} else if (token.equals(">")) {
				deep --;
			} else if (token.equals("extends") || token.equals("super")) {
				params.append(' ');
				params.append(token);
				params.append(' ');
				continue;
			}
			params.append(token);
		}
		return params.toString();
	}

	private static JavaTypeScope parseWildcardParam(JavaTokenizer tokenizer, JavaScopeImpl owner) {
		if (!tokenizer.hasNext()) {
			return new JavaWildcardGenericTypeScopeImpl(owner, null, PrimitiveTypeScopes.OBJECT);
		}
		String bound = tokenizer.next();
		if (bound.equals("super")) {
			tokenizer.next();
			return new JavaWildcardGenericTypeScopeImpl(owner, parseUnknownType(tokenizer, owner), PrimitiveTypeScopes.OBJECT);
		} else if (bound.equals("extends")) {
			tokenizer.next();
			return new JavaWildcardGenericTypeScopeImpl(owner, null,parseUnknownType(tokenizer, owner));
		}
		return new JavaWildcardGenericTypeScopeImpl(owner, null, PrimitiveTypeScopes.OBJECT);
	}
}
